const { progress } = require("../utils/display_tool");
const { eval_code } = require("./share_data");
const { read_plain } = require("../utils/file_tool");
const { pause } = require("../utils/interaction_tool");
const {
    whisper,
    getPrint,
    resetError,
    getLastError,
    warn,
    log,
    error,
    setLastError,
    setLastCommand,
} = require("../utils/log_tool");
const { AbortCommand } = require("./abort");
const { BaseCommand, PROCESS_STATE } = require("./base");
const { BreakCommand } = require("./break");
const { CommitCommand } = require("./commit");
const { ConfirmCommand } = require("./confirm");
const { ContinueCommand } = require("./continue");
const { CopyCommand } = require("./copy");
const { WriteClipboard } = require("./write_clipboard");
const { ReadClipboard } = require("./read_clipboard");
const { DateCommand } = require("./date");
const { DebuggerCommand } = require("./debugger");
const { EvalCommand } = require("./eval");
const { ExecFromFileCommand } = require("./exec_from_file");
const { ExportDiffHeadCommand } = require("./export_diff_head");
const { FilesCommand } = require("./files");
const { FirstExistCommand } = require("./first_exist");
const { FlattenCommand } = require("./flatten");
const { GitCommitCommand } = require("./git_commit");
const { GitExportDiffHeadCommand } = require("./git_export_diff_head");
const { GitIsDirtyCommand } = require("./git_is_dirty");
const { HTTPRequestCommand } = require("./http_request");
const { InputCommand } = require("./input");
const { IsDirtyCommand } = require("./is_dirty");
const { List2DictCommand } = require("./list2dict");
const { LoopCommand } = require("./loop");
const { LoopForeverCommand } = require("./loop_forever");
const { ModifyCommand } = require("./modify");
const { MoveCommand } = require("./move");
const { MultiSelectCommand } = require("./multi_select");
const { OpenCommand } = require("./open");
const { ParseCommand } = require("./parse");
const { PauseCommand } = require("./pause");
const { PrintCommand } = require("./print");
const { ReadCfgCommand } = require("./read_cfg");
const { ReadPlainCommand } = require("./read_plain");
const { RemoveCommand } = require("./remove");
const { RetrieveCommand } = require("./retrieve");
const { RevealCommand } = require("./reveal");
const { SelectCommand } = require("./select");
const { ShellCommand } = require("./shell");
const { SleepCommand } = require("./sleep");
const { StoreCommand } = require("./store");
const { StringifyCommand } = require("./stringify");
const { SvnCommitCommand } = require("./svn_commit");
const { SVNExportDiffHeadCommand } = require("./svn_export_diff_head");
const { SvnIsDirtyCommand } = require("./svn_is_dirty");
const { SvnUpdateCommand } = require("./svn_update");
const { TestCommand } = require("./test");
const { TruncateCommand } = require("./truncate");
const { TruncateAllCommand } = require("./truncate_all");
const { ValidRequiredCommand } = require("./valid_required");
const { VoidCommand } = require("./void");
const { WhichCommand } = require("./which");
const { WriteCfgCommand } = require("./write_cfg");
const { WritePlainCommand } = require("./write_plain");
const { ZipCommand } = require("./zip");
const { MoveLocalCommand } = require("./move_local");
const { EditCommand } = require("./edit");
const { ScheduleCommand } = require("./schedule");
const { AlertCommand } = require("./alert");
const { IntervalCommand } = require("./interval");
const { TraversalCommand } = require("./traversal");
const { NotifyCommand } = require("./notify");
const { LogCommand } = require("./log");
const { formatData, shareData } = require("./share_data");
const { FileInfoCommand } = require("./file_info");
const { ReadOnlyExcelCommand } = require("./read_only_excel");
const { WebSocketCommand } = require("./web_socket");
const { TsSortCommand } = require("./ts_sort");
const { DivinationCommand } = require("./divination");
const { FileSizeCommand } = require("./file_size");
const { TmpdirCommand } = require("./tmpdir");
const { HashCommand } = require("./hash");
const { StoreOptionalCommand } = require("./store_optional");
const { UpdatingRevisionCommand } = require("./updating_revision");
const { RevisionInfoCommand } = require("./revision_info");
const { UnzipCommand } = require("./unzip");
const { DownloadCommand } = require("./download");
const { ReadExcelCommand } = require("./read_excel");
const { WriteExcelCommand } = require("./write_excel");
const { EditExcelCommand } = require("./edit_excel");
const { MysqlConnectCommand } = require("./mysql_connect");
const { MysqlEndCommand } = require("./mysql_end");
const { MysqlQueryCommand } = require("./mysql_query");
const { FtpCommand } = require("./ftp");
const { StreamCommand } = require("./stream");
const { TencentCosCommand } = require("./tencent_cos");
const { WriteCacheCommand } = require("./write_cache");
const { ReadCacheCommand } = require("./read_cache");
const { AssembleCommand } = require("./Assemble");
const { DisassembleCommand } = require("./disassemble");
const { DingtalkCommand } = require("./dingtalk");
const { WatchMailCommand } = require("./watch_mail");
const { TranslateCommand } = require("./translate");
const { ChangeFilterCommand } = require("./change_filter");
const { TempCommand } = require("./temp");
const { CheerioCommand } = require("./cheerio");
const { SwitchCommand } = require("./switch");
const { MergeFilesCommand } = require("./merge_files");
const { JwtDecodeCommand } = require("./jwt_decode");
const { ClearScreenCommand } = require("./clear_screen");
const { TitleCommand } = require("./title");
const { CompareCommand } = require("./compare");
const { ZipDataCommand } = require("./zip_data");
const { EnvOptionalCommand } = require("./env_optional");
const { SendEmailCommand } = require("./send_email");
const { AssertCommand } = require("./assert");
const { SvnAddCommand } = require("./svn_add");
const { ImageCropCommand } = require("./image_crop");
const { CursorUpCommand } = require("./cursor_up");
const {
    RegisterSearchInstallLocationCommand,
} = require("./register_search_install_location");
const { DeobfuscateCommand } = require("./deobfuscate");
const { AliOssCommand } = require("./ali_oss");
const { BackupCommand } = require("./backup");
const { CompressCommand } = require("./compress");
const { Word2txtCommand } = require("./word2txt");
const { ObfuscateCommand } = require("./obfuscate");
const { SplitFileCommand } = require("./split_file");
const { MergeFileCommand } = require("./merge_file");
const { ZipFolderCommand } = require("./zip_folder");

const globalData = {
    executed_cfg: [], // 执行过的配置文件
    process_state: PROCESS_STATE.NONE, // 当前执行状态
};

/**
 * @type { { [commandType: string]: BaseCommand[] } }
 */
const commandMap = {};

/**
 * @type { { [commandType: string]: BaseCommand[] } }
 */
const commandPool = {};
const showLogs = {};

function register(type, commandCls, showLog) {
    commandMap[type] = commandCls;
    showLogs[type] = showLog;
}

/**
 * 获取命令
 * @param {string} type
 * @returns {BaseCommand}
 */
function getCommand(type) {
    const commandCls = commandMap[type];
    if (!commandCls) {
        return;
    }
    let commands = commandPool[type];
    if (!commands) {
        commands = commandPool[type] = [];
    }
    let command = commands.pop();
    if (!command) {
        command = new commandCls(type, globalData, execSingle, exec);
    }
    return command;
}

/**
 * 回收命令
 * @param {BaseCommand} command
 */
function recyleCommand(command) {
    if (!command) {
        warn("回收命令异常");
        return;
    }
    const type = command.type;
    let commands = commandPool[type];
    if (!commands) {
        commands = commandPool[type] = [];
    }
    commands.push(command);
}

/**
 *
 * @param {{type: string, title?: string, when:?: string, data: any, ignore_error: boolean}[]} commandCfgs
 */
async function exec(commandCfgs, depth = 0) {
    for (let i = 0, len = commandCfgs?.length; i < len; ++i) {
        const commandCfg = commandCfgs[i];
        await execSingle(commandCfg, depth, {
            curr: i,
            total: len,
        });
        if (globalData.process_state === PROCESS_STATE.BREAK) {
            break;
        } else if (globalData.process_state === PROCESS_STATE.CONTINUE) {
            break;
        }
    }
}

/**
 *
 * @param {*} depth
 */
async function execSingle(commandCfg) {
    const totalRetryTimes = commandCfg.retry || 0;
    let retryTimes = 0;
    let ret = null;
    while (true) {
        if (retryTimes > 0) {
            // 仅在重试时，重置错误
            setLastError(null);
        }
        try {
            if (commandCfg.async) {
                _execSingle.apply(this, arguments);
            } else {
                ret = await _execSingle.apply(this, arguments);
            }
        } catch (e) {
            setLastError(e);
        }
        if (getLastError()) {
            if (++retryTimes <= totalRetryTimes) {
                error(`\n${getLastError()}`);
                warn(`重新执行 (${retryTimes}/${totalRetryTimes})...`);
                await new Promise((resolve) => {
                    setTimeout(() => {
                        resolve();
                    }, 1000 * retryTimes);
                });
                continue;
            }
            throw getLastError();
        }
        break;
    }
    return ret;
}

/**
 *
 * @param {*} depth
 */
async function _execSingle(commandCfg, depth, progressData) {
    if (typeof commandCfg === "string") {
        commandCfg = {
            type: commandCfg,
        };
    }
    const rawCfg = JSON.stringify(commandCfg, undefined, 4);
    setLastCommand(rawCfg);
    // 深度拷贝
    commandCfg = JSON.parse(rawCfg);
    // 提前format
    if (!commandCfg.raw) {
        formatData(commandCfg);
        setLastCommand(rawCfg, commandCfg);
    }
    if (commandCfg.side_effect && shareData.DRY_RUN) {
        warn(`[DRY_RUN] ${commandCfg.title || commandCfg.type}`);
        return;
    }
    const type = commandCfg.type;
    const data = commandCfg.data;
    const command = getCommand(type);
    if (!command) {
        log("");
        whisper(`${getPrint(commandCfg, true)}`);
        throw `未注册的命令 ${type}`;
    }
    // 判断是否步进执行
    if (command.shareData.STEP_BY_STEP) {
        await pause(`即将执行 ${rawCfg}`);
    }
    if (commandCfg.debugger) {
        if (typeof commandCfg.debugger === "boolean") {
        } else {
            try {
                if (eval_code(commandCfg.debugger)) {
                }
            } catch (err) {
                warn(err);
            }
        }
    }

    if (!(await command.checkValid(commandCfg))) {
        return;
    }
    if (
        (commandCfg.show_progress || commandCfg.title) /* !== false */ &&
        progressData &&
        shareData.$COMPRESS_PROGERSS <= 0
    ) {
        progress(progressData.curr, progressData.total, {
            depth: depth,
            desc: `> ${commandCfg.title || commandCfg.type}`,
            color: "white",
        });
        log("");
    }

    if (showLogs[commandCfg.type]) {
        log("");
    }

    let content;
    if (command.getRequireContent()) {
        if (data.content != null) {
            content = data.content;
        } else if (data.key) {
            const keys = data.key.split(".");
            content = command.shareData;
            for (let i = 0; i < keys.length; i++) {
                const key = keys[i];
                content = content[key];
                if (!content) {
                    break;
                }
            }
        } else if (data.src) {
            content = read_plain(data.src);
        } else if (data.code) {
            content = eval_code(data.code);
        }
        if (content == null) {
            if (!commandCfg.ignore_error) {
                throw `无content可供使用`;
            }
        }
    }
    let ret;
    let is_first = true;
    do {
        if (!is_first) {
            warn(`结果不合法,请检查...`);
        }
        ret = await command.handle(commandCfg, content, depth);
        is_first = false;
    } while (!ret && commandCfg.required);
    recyleCommand(command);

    if (!getLastError()) {
        if (
            commandCfg.show_progress /* !== false */ &&
            progressData &&
            shareData.$COMPRESS_PROGERSS <= 0
        ) {
            progress(progressData.curr + 1, progressData.total, {
                depth: depth,
                desc: `✔ ${commandCfg.title || commandCfg.type}`.cyan,
            });
        }
    } else {
        if (
            commandCfg.show_progress /* !== false */ &&
            progressData &&
            shareData.$COMPRESS_PROGERSS <= 0
        ) {
            progress(progressData.curr + 1, progressData.total, {
                depth: depth,
                desc: `✖ ${commandCfg.title || commandCfg.type}`.red,
            });
        }
        if (commandCfg.ignore_error) {
            resetError(commandCfg.ignore_error);
        } else {
            throw getLastError();
        }
    }
    if (commandCfg.newline) {
        log("");
    }

    return ret;
}

function init() {
    register("abort", AbortCommand, false);
    register("base", BaseCommand, false);
    register("confirm", ConfirmCommand, false);
    register("copy", CopyCommand, false);
    register("write_clipboard", WriteClipboard, false);
    register("read_clipboard", ReadClipboard, false);
    register("date", DateCommand, false);
    register("debugger", DebuggerCommand, false);
    register("eval", EvalCommand, false);
    register("exec_from_file", ExecFromFileCommand, true);
    register("exec_shell", ShellCommand, false);
    register("files", FilesCommand, false);
    register("first_exist", FirstExistCommand, false);
    register("flatten", FlattenCommand, false);
    register("git_commit", GitCommitCommand, false);
    register("git_export_diff_head", GitExportDiffHeadCommand, false);
    register("git_is_dirty", GitIsDirtyCommand, false);
    register("http_request", HTTPRequestCommand, false);
    register("input", InputCommand, true);
    register("list2dict", List2DictCommand, false);
    register("loop", LoopCommand, false);
    register("loop_forever", LoopForeverCommand, false);
    register("move", MoveCommand, false);
    register("multi_select", MultiSelectCommand, false);
    register("modify", ModifyCommand, false);
    register("open", OpenCommand, false);
    register("parse", ParseCommand, false);
    register("pause", PauseCommand, false);
    register("print", PrintCommand, false);
    register("read_cfg", ReadCfgCommand, false);
    register("read_plain", ReadPlainCommand, false);
    register("remove", RemoveCommand, false);
    register("request", HTTPRequestCommand, false);
    register("retrieve", RetrieveCommand, false);
    register("reveal", RevealCommand, false);
    register("select", SelectCommand, false);
    register("shell", ShellCommand, false);
    register("sleep", SleepCommand, false);
    register("storage", StoreCommand, false);
    register("store", StoreCommand, false);
    register("storage_optional", StoreOptionalCommand, false);
    register("store_optional", StoreOptionalCommand, false);
    register("svn_commit", SvnCommitCommand, false);
    register("svn_is_dirty", SvnIsDirtyCommand, false);
    register("svn_update", SvnUpdateCommand, false);
    register("svn_export_diff_head", SVNExportDiffHeadCommand, false);
    register("test", TestCommand, false);
    register("truncate", TruncateCommand, false);
    register("truncate_all", TruncateAllCommand, false);
    register("void", VoidCommand, false);
    register("write_cfg", WriteCfgCommand, false);
    register("write_plain", WritePlainCommand, false);
    register("zip", ZipCommand, false);
    register("valid_required", ValidRequiredCommand, false);
    register("continue", ContinueCommand, false);
    register("break", BreakCommand, false);
    register("stringify", StringifyCommand, false);
    register("commit", CommitCommand, false);
    register("export_diff_head", ExportDiffHeadCommand, false);
    register("is_dirty", IsDirtyCommand, false);
    register("which", WhichCommand, false);
    register("move_local", MoveLocalCommand, false);
    register("edit", EditCommand, false);
    register("schedule", ScheduleCommand, false);
    register("alert", AlertCommand, false);
    register("interval", IntervalCommand, false);
    register("traversal", TraversalCommand, false);
    register("notify", NotifyCommand, false);
    register("log", LogCommand, false);
    register("file_info", FileInfoCommand, false);
    register("read_only_excel", ReadOnlyExcelCommand, false);
    register("web_socket", WebSocketCommand, false);
    register("ts_sort", TsSortCommand, false);
    register("suan_gua", DivinationCommand, false);
    register("divination", DivinationCommand, false);
    register("file_size", FileSizeCommand, false);
    register("tmpdir", TmpdirCommand, false);
    register("hash", HashCommand, false);
    register("updating_revision", UpdatingRevisionCommand, false);
    register("revision_info", RevisionInfoCommand, false);
    register("unzip", UnzipCommand, false);
    register("download", DownloadCommand, false);
    register("read_excel", ReadExcelCommand, false);
    register("write_excel", WriteExcelCommand, false);
    register("edit_excel", EditExcelCommand, false);
    register("mysql_connect", MysqlConnectCommand, false);
    register("mysql_end", MysqlEndCommand, false);
    register("mysql_query", MysqlQueryCommand, false);
    register("ftp", FtpCommand, false);
    register("stream", StreamCommand, false);
    register("tencent_cos", TencentCosCommand, false);
    register("read_cache", ReadCacheCommand, false);
    register("write_cache", WriteCacheCommand, false);
    register("assemble", AssembleCommand, false);
    register("disassemble", DisassembleCommand, false);
    register("dingtalk", DingtalkCommand, false);
    register("watch_mail", WatchMailCommand, false);
    register("translate", TranslateCommand, false);
    register("change_filter", ChangeFilterCommand, false);
    register("temp", TempCommand, false);
    register("cheerio", CheerioCommand, false);
    register("switch", SwitchCommand, false);
    register("merge_files", MergeFilesCommand, false);
    register("jwt_decode", JwtDecodeCommand, false);
    register("clear_screen", ClearScreenCommand, false);
    register("title", TitleCommand, false);
    register("compare", CompareCommand, false);
    register("zip_data", ZipDataCommand, false);
    register("env_optional", EnvOptionalCommand, false);
    register("send_email", SendEmailCommand, false);
    register("assert", AssertCommand, false);
    register("svn_add", SvnAddCommand, false);
    register("image_crop", ImageCropCommand, false);
    register("cursor_up", CursorUpCommand, false);
    register(
        "register_search_install_location",
        RegisterSearchInstallLocationCommand,
        false,
    );
    register("deobfuscate", DeobfuscateCommand, false);
    register("ali_oss", AliOssCommand, false);
    register("backup", BackupCommand, false);
    register("compress", CompressCommand, false);
    register("word2txt", Word2txtCommand, false);
    register("obfuscate", ObfuscateCommand, false);
    register("split_file", SplitFileCommand, false);
    register("merge_file", MergeFileCommand, false);
    register("zip_folder", ZipFolderCommand, false);
}

module.exports = {
    init,
    exec,
    execSingle,
};
